/*****************************************************************************
 * dm2c.c
 *
 * Copyright (C) 2020 Jeasonvor 1101627719@qq.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 ****************************************************************************/

/*****************************************************************************
 * Included Files
 ****************************************************************************/
#include "plum_api.h"
#include "thirdparty/modbus/modbus.h"
#include "hardware/steper/steper.h"
/*****************************************************************************
 * Trace Definitions
 ****************************************************************************/
#define LOG_RUN_LEVEL LOG_LEVEL_INFO
#define LOG_MODULE    "hardware.steper"

/*****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define STEPER_DM2C_PR_BASE         0x6200
#define STEPER_DM2C_OFFSET_MODE     0
#define STEPER_DM2C_OFFSET_POS_H    1
#define STEPER_DM2C_OFFSET_POS_L    2
#define STEPER_DM2C_OFFSET_SPD      3
#define STEPER_DM2C_OFFSET_SPD_UP   4
#define STEPER_DM2C_OFFSET_SPD_DOWN 5
#define STEPER_DM2C_OFFSET_SPD_STOP 6
#define STEPER_DM2C_OFFSET_SPD_MAP  7
#define STEPER_DM2C_PR_GROUP        8

#define STEPER_DM2C_DIRECTION   0x0007
#define STEPER_DM2C_TIRP        0x6002
#define STEPER_DM2C_SPEED       0x6203
#define STEPER_DM2C_STATE       0x1003
#define STEPER_DM2C_OUT_CURRENT 0x0191

#define STEPER_DM2C_IDX_DIRECTION 0
#define STEPER_DM2C_IDX_TIRP      1
#define STEPER_DM2C_IDX_SPEED     2
#define STEPER_DM2C_IDX_STATE     3

/*****************************************************************************
 * Private Types
 ****************************************************************************/

/** 步进电机模式 */
typedef enum {
    DM2C_MODE_ABSOLUTE_POSITION = 0x01, /** 绝对位置模式    **/
    DM2C_MODE_RELATIVE_POSITION = 0x41, /** 相对位置模式    **/
    DM2C_MODE_SPEED             = 0x02, /** 速度模式        **/
} dm2c_mode_t;

/** 步进电机启停 **/
typedef enum {
    DM2C_RUN_START, /** 启动 **/
    DM2C_RUN_STOP,  /** 停止 **/
} dm2c_run_t;

typedef struct {
    plum_u16 addr;
    plum_u16 data;
} dm2c_reg_t;

/*****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/*****************************************************************************
 * Private Data
 ****************************************************************************/

PLUM_PRIVATE
dm2c_reg_t dm2c_tb[] = {
    [STEPER_DM2C_IDX_DIRECTION] = {STEPER_DM2C_DIRECTION, 0},
    [STEPER_DM2C_IDX_TIRP]      = {STEPER_DM2C_TIRP, 0},
    [STEPER_DM2C_IDX_SPEED]     = {STEPER_DM2C_SPEED, 0},
    [STEPER_DM2C_IDX_STATE]     = {STEPER_DM2C_STATE, 0}};

/**
 * @brief： 本地map表(两个字节一组)
 *
 * start addr   |       type        |     remark    |
 *      0       |     run state     |        r      |
 *      1       |                   | 0.停止，1.运行 |
 *
 *      2       |   run direction   |        rw     |
 *      3       |                   |  0.正，1.负    |
 *
 *      4       |     run speed     |        rw     |
 *      5       |                   |               |
 *
 *      6       |   output current  |       rw      |
 *      7       |                   |  max current  |
 */
/*****************************************************************************
 * Public Data
 ****************************************************************************/

/*****************************************************************************
 * Private Functions
 ****************************************************************************/

PLUM_PRIVATE
plum_void steper_dm2c_notice(modbus_protocol_evt evt, plum_void *arg)
{
    switch (evt) {
        case MODBUS_EVT_MSG:
        {
            modbus_msg_t *msg = (modbus_msg_t *)arg;
            plum_u32      loop;
            for (loop = 0; loop < PLUM_ARRAY_CNT(dm2c_tb); loop++) {
                if (dm2c_tb[loop].addr == msg->addr) {
                    dm2c_tb[loop].data = *(plum_u16 *)msg->value;
                    break;
                }
            }
        } break;

        case MODBUS_EVT_ERR:
        {
            modbus_err_t *msg = (modbus_err_t *)arg;
            LOG_E("modbus notice error,rc : %d", msg->errcode);
            LOG_E("reason : %s", msg->discr);
        } break;

        default:
            break;
    }
}

/**
 * @brief： 设置步进电机运行方向
 *
 * @param[in]:  id      -       步进电机索引（modbus子站地址 ）
 * @param[in]:  ch      -       通道，对应dm2c为PR通道（0~8）
 * @param[in]:  dir     -       方向
 *
 * @return:
 */
PLUM_PRIVATE
PLUM_INLINE plum_void steper_dm2c_set_direciton(plum_u32 id, plum_u32 ch,
                                                steper_direction_t dir)
{
    PLUM_UNUSED(ch);

    plum_u16 data = dir;
    modbus_protocol_write(MODBUS_PROTOCOL_RTU, id, MODBUS_PROTOCOL_WRITE_SHOLD,
                          dm2c_tb[STEPER_DM2C_IDX_DIRECTION].addr, &data, 2);
}

PLUM_PRIVATE
plum_s32 steper_dm2c_get_direction(plum_u32 id, plum_u32 ch)
{
    PLUM_UNUSED(ch);

    plum_s32 rc = PLUM_ECODE_OK;

    do {
        rc = modbus_protocol_register_notice(steper_dm2c_notice);
        if (rc) {
            LOG_E("modbus_protocol_register_notice err,rc : %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

        rc = modbus_protocol_read(MODBUS_PROTOCOL_RTU, id,
                                  MODBUS_PROTOCOL_READ_SHOLD,
                                  dm2c_tb[STEPER_DM2C_IDX_DIRECTION].addr, 1);
        if (rc) {
            LOG_E("modbus_protocol_read err,rc : %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

        rc = dm2c_tb[STEPER_DM2C_IDX_DIRECTION].data;
    } while (0);

    return (rc);
}

/**
 * @brief:  设置步进电机工作模式
 *
 * @param[in]:  id      -       步进电机索引（modbus子站地址 ）
 * @param[in]:  ch      -       通道，对应dm2c为PR通道（0~8）
 * @param[in]:  mode    -       模式
 *
 * @return:
 */
PLUM_PRIVATE
PLUM_INLINE plum_void steper_dm2c_mode(plum_u32 id, plum_u32 ch,
                                       dm2c_mode_t mode)
{
    PLUM_UNUSED(ch);

    plum_u16 addr = STEPER_DM2C_PR_BASE + (ch << 3) + STEPER_DM2C_OFFSET_MODE;
    plum_u16 data = mode;
    modbus_protocol_write(MODBUS_PROTOCOL_RTU, id, MODBUS_PROTOCOL_WRITE_SHOLD,
                          addr, &data, 2);
}

/**
 * @brief:  步进电机启停
 *
 * @param[in]:  id      -       步进电机索引（modbus子站地址 ）
 * @param[in]:  ch      -       通道
 *
 * @return：
 */
PLUM_PRIVATE
PLUM_INLINE plum_void steper_dm2c_ctrl(plum_u32 id, plum_u32 ch, dm2c_run_t run)
{
    PLUM_UNUSED(ch);

    plum_u16 data = (run == DM2C_RUN_START) ? 0x0010 : 0x0040;
    modbus_protocol_write(MODBUS_PROTOCOL_RTU, id, MODBUS_PROTOCOL_WRITE_SHOLD,
                          STEPER_DM2C_TIRP, &data, 2);
}

/**
 * @brief:  设置步进电机运行速度
 *
 * @param[in]:  id      -       步进电机索引（modbus子站地址 ）
 * @param[in]:  ch      -       通道
 * @param[in]:  speed   -       速度
 *
 * @return:
 */
PLUM_PRIVATE
PLUM_INLINE plum_void steper_dm2c_set_speed(plum_u32 id, plum_u32 ch,
                                            plum_u32 speed)
{
    PLUM_UNUSED(ch);

    plum_u16 addr = STEPER_DM2C_PR_BASE + (ch << 3) + STEPER_DM2C_OFFSET_SPD;
    plum_u16 data = speed;
    modbus_protocol_write(MODBUS_PROTOCOL_RTU, id, MODBUS_PROTOCOL_WRITE_SHOLD,
                          addr, &data, 2);
}

/**
 * @brief： 设置输出峰值电流
 *
 * @param[in]:  id      -   步进电机索引
 * @param[in]:  mA      -   输出峰值电流
 *
 * @return
 */
PLUM_PRIVATE
PLUM_INLINE plum_void steper_dm2c_set_output_current(plum_u32 id, plum_u32 mA)
{
    plum_u16 addr = STEPER_DM2C_OUT_CURRENT;
    plum_u16 data = mA / 100;
    modbus_protocol_write(MODBUS_PROTOCOL_RTU, id, MODBUS_PROTOCOL_WRITE_SHOLD,
                          addr, &data, 2);
}

PLUM_PRIVATE
plum_s32 steper_dm2c_get_speed(plum_u32 id, plum_u32 ch)
{
    PLUM_UNUSED(ch);

    plum_s32 rc = PLUM_ECODE_OK;

    do {
        rc = modbus_protocol_register_notice(steper_dm2c_notice);
        if (rc) {
            LOG_E("modbus_protocol_register_notice err,rc : %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

        rc = modbus_protocol_read(MODBUS_PROTOCOL_RTU, id,
                                  MODBUS_PROTOCOL_READ_SHOLD,
                                  dm2c_tb[STEPER_DM2C_IDX_SPEED].addr, 1);
        if (rc) {
            LOG_E("modbus_protocol_read err,rc : %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

        rc = dm2c_tb[STEPER_DM2C_IDX_SPEED].data;
    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 steper_dm2c_get_state(plum_u32 id)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        rc = modbus_protocol_register_notice(steper_dm2c_notice);
        if (rc) {
            LOG_E("modbus_protocol_register_notice err,rc : %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

        rc = modbus_protocol_read(MODBUS_PROTOCOL_RTU, id,
                                  MODBUS_PROTOCOL_READ_SHOLD,
                                  dm2c_tb[STEPER_DM2C_IDX_STATE].addr, 1);
        if (rc) {
            LOG_E("modbus_protocol_read err,rc : %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

        rc = dm2c_tb[STEPER_DM2C_IDX_STATE].data;
    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 steper_dm2c_init(plum_device_ref_t dev)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        PLUM_UNUSED(dev);
    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 steper_dm2c_read(plum_device_ref_t dev, plum_dev_read_t *arg)
{
    plum_s32 rc = PLUM_ECODE_OK;
    PLUM_UNUSED(dev);

    do {
        switch (arg->offset) {
            case 0:
                rc = steper_dm2c_get_state(1);
                if (rc < 0) {
                    LOG_E("steper_dm2c_get_state err,rc : %d", rc);
                    rc = PLUM_ECODE_EIO;
                    break;
                }
                *(arg->rData) = PLUM_GET_BIT(rc, 2);
                // memcpy(arg->rData, &rc, 2);
                break;

            case 2:
                rc = steper_dm2c_get_direction(1, 0);
                if (rc < 0) {
                    LOG_E("steper_dm2c_get_direction err,rc : %d", rc);
                    rc = PLUM_ECODE_EIO;
                    break;
                }
                memcpy(arg->rData, &rc, 2);
                break;

            case 4:
                rc = steper_dm2c_get_speed(1, 0);
                if (rc < 0) {
                    LOG_E("steper_dm2c_get_direction err,rc : %d", rc);
                    rc = PLUM_ECODE_EIO;
                    break;
                }
                memcpy(arg->rData, &rc, 2);
                break;
        }
    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 steper_dm2c_write(plum_device_ref_t dev, plum_dev_write_t *arg)
{
    PLUM_UNUSED(dev);

    plum_s32 rc = PLUM_ECODE_OK;

    do {
        switch (arg->offset) {
            case 0x06:
            {
                plum_u16 value = *(plum_u16 *)arg->wData;
                LOG_I("set output current : %d mA", value);
                steper_dm2c_set_output_current(1, value);
            } break;

            default:
                LOG_E("not support the offset: %d", arg->offset);
                break;
        }
    } while (0);

    return (rc);
}

PLUM_PRIVATE
plum_s32 steper_dm2c_ioctrl(plum_device_ref_t dev, plum_dev_ioctrl_t *arg)
{
    PLUM_UNUSED(dev);

    plum_s32 rc = PLUM_ECODE_OK;

    LOG_D("steper_dm2c_ioctrl");
    do {
        switch (arg->id) {
            case PLUM_SENSOR_IOCTL_STEPER_SPD_START:
            {
                LOG_I("PLUM_SENSOR_IOCTL_STEPER_SPD_START");

                plum_sensor_ioctl_steper_spd_start_t *spd =
                    (plum_sensor_ioctl_steper_spd_start_t *)arg->wData;
                rc = dev_steper_speed_run(spd->id, spd->direction, spd->speed);
            } break;

            case PLUM_SENSOR_IOCTL_STEPER_ANG_START:
                break;

            case PLUM_SENSOR_IOCTL_STEPER_STOP:
            {
                LOG_I("PLUM_SENSOR_IOCTL_STEPER_STOP");
                plum_sensor_ioctl_steper_stop_t *stop =
                    (plum_sensor_ioctl_steper_stop_t *)arg->wData;
                dev_steper_stop(stop->id);
            } break;

            default:
                break;
        }
    } while (0);

    return (rc);
}

/*****************************************************************************
 * Public Functions
 ****************************************************************************/

PLUM_PUBLIC
plum_s32 dev_steper_init(steper_read_t read, steper_write_t write)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        PLUM_UNUSED(read);
        PLUM_UNUSED(write);
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 dev_steper_speed_run(plum_u32 id, steper_direction_t direction,
                              plum_u32 speed)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        steper_dm2c_set_direciton(id, 0, direction);

        steper_dm2c_mode(id, 0, DM2C_MODE_SPEED);

        steper_dm2c_set_speed(id, 0, speed);

        steper_dm2c_ctrl(id, 0, DM2C_RUN_START);
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 dev_steper_stop(plum_u32 id)
{
    steper_dm2c_ctrl(id, 0, DM2C_RUN_STOP);

    return (PLUM_ECODE_OK);
}

PLUM_PRIVATE
plum_dev_api_t dm2c_api = {
    .init   = steper_dm2c_init,
    .read   = steper_dm2c_read,
    .write  = steper_dm2c_write,
    .ioctrl = steper_dm2c_ioctrl,
};
PLUM_DEVICE_EXPORT(dm2c, &dm2c_api);

/*****************************************************************************
 * Cli Functions
 ****************************************************************************/

#if (CONFIG_CAL_CLI_ENABLE)
PLUM_PRIVATE
plum_void dev_steper_cli_run(plum_u32 argc, plum_s8 **argv)
{
    if (argc != 3) {
        LOG_E("the param is err");
        LOG_I("format: steper_ctrl id dirction(0/1)  speed");
        return;
    }

    plum_u32 id        = strtoul((const char *)argv[0], NULL, 10);
    plum_u32 direction = strtoul((const char *)argv[1], NULL, 10);
    plum_u32 speed     = strtoul((const char *)argv[2], NULL, 0);
    LOG_I("id       : %d", id);
    LOG_I("direction: %d", direction);
    LOG_I("speed    : %d", speed);

    dev_steper_speed_run(id, direction, speed);
}
PLUM_CAL_CLI_EXPORT("steper_ctrl", "ctrol steper run", dev_steper_cli_run);

#endif
/****************************************************************************/
/*                                                                          */
/*  End of file.                                                            */
/*                                                                          */
/****************************************************************************/
